﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using CoreEx.Common.Data;
using CoreEx.Common.Extensions;
using CoreEx.Database.Metadata;
using CoreEx.DynamicInvoker;
using LinFu.IoC.Configuration;

namespace CoreEx.Database.Linq.Implementation
{
    [Implements(typeof(IRelationMapper))]
    public class RelationMapper : IRelationMapper 
    {
        private IDictionary<PropertyInfo,PropertyInfo> _ownerPropertMap = new Dictionary<PropertyInfo, PropertyInfo>();

        private IList<PropertyInfo> _mappedProperties = new List<PropertyInfo>();


        public void MapRelations(ITranslateResult_old translateResult)
        {                                       
            var childProperties = translateResult.TypeMappingInfo.ColumnMappings.Where(
                    cm => cm.Value.RelationType == CardinalityType.OneToMany).Select(cm => cm.Value.Property);

            foreach (var childProperty in childProperties)
            {                
                if (translateResult.MemberResults.Keys.Contains(childProperty) && !_mappedProperties.Contains(childProperty))
                {
                    var childTranslateresult = translateResult.MemberResults[childProperty];
                    MapChildRelation(translateResult,childTranslateresult,childProperty);
                    _mappedProperties.Add(childProperty);
                    if (_ownerPropertMap.ContainsKey(childProperty))
                        _mappedProperties.Add(_ownerPropertMap[childProperty]);
                }                
            }

            var parentProperties = translateResult.TypeMappingInfo.ColumnMappings.Where(
                    cm => cm.Value.RelationType == CardinalityType.ManyToOne).Select(cm => cm.Value.Property);


            foreach (var parentProperty in parentProperties)
            {
                if (translateResult.MemberResults.Keys.Contains(parentProperty) && !_mappedProperties.Contains(parentProperty))
                {
                    var parentTranslateResult = translateResult.MemberResults[parentProperty];
                    MapParentRelation(translateResult,parentTranslateResult,parentProperty);
                    _mappedProperties.Add(parentProperty);
                }

            }



            foreach (var memberResult in translateResult.MemberResults.Values)
            {
                MapRelations(memberResult);
            }


        }


        private void MapParentRelation(ITranslateResult_old translateResult, ITranslateResult_old parentTranslateResult, PropertyInfo propertyInfo)
        {
            var foreignKeyInfo = translateResult.TypeMappingInfo.ColumnMappings[propertyInfo].ForeignKeyInfo;
            var foreignkeyColumns = foreignKeyInfo.Columns.Select(c => c.ForeignColumnInfo).ToArray();
            for (int i = 0; i < translateResult.Result.Count; i++)
            {
                var target = translateResult.Result[i];
                var row = translateResult.Table.Rows[i];
                int foreignKeyHash = HashKey(GetKeyValues(row, foreignkeyColumns));                
                PopulateParentProperty(foreignKeyHash,parentTranslateResult,propertyInfo,target);
            }


        }


        private void PopulateParentProperty(int foreignKeyHash, ITranslateResult_old parentTranslateResult, PropertyInfo propertyInfo, object target)
        {
            if (foreignKeyHash == 0)
                return;
            var primaryKeyColumns = parentTranslateResult.TableInfo.PrimaryKey.Columns.ToArray();

            for (int i = 0; i < parentTranslateResult.Table.Rows.Count; i++)
            {
                var row = parentTranslateResult.Table.Rows[i];
                var primaryKeyHash = HashKey(GetKeyValues(row, primaryKeyColumns));
                if (primaryKeyHash == foreignKeyHash)
                {
                    propertyInfo.DynamicInvoke(target, parentTranslateResult.Result[i]);
                    break;
                }
            }

        }



        private void MapChildRelation(ITranslateResult_old translateResult, ITranslateResult_old childResult, PropertyInfo propertyInfo)
        {            
            var primaryKeyColumns = translateResult.TableInfo.PrimaryKey.Columns.ToArray();

            var foreignkeyColumns =
                translateResult.TypeMappingInfo.ColumnMappings[propertyInfo].ForeignKeyInfo.Columns.Select(
                    fkc => fkc.ForeignColumnInfo).ToArray();

            for (int i = 0; i < translateResult.Result.Count; i++)
            {
                var target = translateResult.Result[i];
                var row = translateResult.Table.Rows[i];
                int primaryHashKey = HashKey(GetKeyValues(row, primaryKeyColumns));
                var targetList = (IList)propertyInfo.DynamicInvoke(target);                              
                PopulateChildProperty(primaryHashKey,childResult,foreignkeyColumns,targetList);
                
                if (!_ownerPropertMap.ContainsKey(propertyInfo))
                {
                    var ownerProperty = childResult.ProjectedType.GetProperties().Where(
                        p => p.PropertyType.IsAssignableFrom(translateResult.ProjectedType)).FirstOrDefault();
                    if (ownerProperty != null)
                        _ownerPropertMap.Add(propertyInfo,ownerProperty);
                }

                if (_ownerPropertMap.ContainsKey(propertyInfo))
                {
                    foreach (var item in targetList)
                    {
                        if (!_mappedProperties.Contains(_ownerPropertMap[propertyInfo]))
                        {
                            _ownerPropertMap[propertyInfo].DynamicInvoke(item, target);                            
                        }
                    }
                }

            }
        }


        private void PopulateChildProperty(int primaryHashKey, ITranslateResult_old childResult, ColumnInfo[] foreignKeyColumns, IList targetList)
        {            
            for (int i = 0; i < childResult.Table.Rows.Count; i++)
            {
                var row = childResult.Table.Rows[i];
                int foreignHashKey = HashKey(GetKeyValues(row, foreignKeyColumns));
                if (primaryHashKey == foreignHashKey)
                    targetList.Add(childResult.Result[i]);
            }
        }


        private int HashKey(object[] values)
        {
            int hashCode = 0;
            foreach (var value in values)
            {
                if (value == null)
                    return 0;
                hashCode = hashCode ^ value.GetHashCode();                                           
            }
            return hashCode;
        }

        private object[] GetKeyValues(IRow row, ColumnInfo[] keyColumns)
        {
            var result = new object[keyColumns.Length];
            for (int i = 0; i < keyColumns.Length; i++)
            {
                result[i] = row[keyColumns[i].ColumnName];
            }
            return result;
        }


        

    }
}
